
// Copyright (C) 2011 Luca Piccioni
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;

namespace Derm.Render
{
	/// <summary>
	/// A map between attribute and semantic.
	/// </summary>
	[XmlType("Semantic")]
	public class ShaderAttributeSemantic
	{
		/// <summary>
		/// The attribute name.
		/// </summary>
		[XmlAttribute("Name")]
		public string AttributeName;

		/// <summary>
		/// The attribute semantic.
		/// </summary>
		[XmlAttribute("Semantic")]
		public string AttributeSemantic;
	}

	/// <summary>
	/// A shader library program.
	/// </summary>
	[XmlType("ShaderProgram")]
	public class ShaderLibraryProgram
	{
		/// <summary>
		/// Reference to a shader library object.
		/// </summary>
		public struct LibraryObjectRef
		{
			/// <summary>
			/// The shader library object identifier.
			/// </summary>
			[XmlText()]
			public string ObjectId;

			/// <summary>
			/// The interface implemented by the referenced shader library object.
			/// </summary>
			[XmlAttribute("Interface")]
			public string Interface;
		}

		/// <summary>
		/// Shader library program class name.
		/// </summary>
		[XmlAttribute("ClassName")]
		public string ClassName;

		/// <summary>
		/// Vertex shader objects composing this shader library program.
		/// </summary>
		[XmlArray("VertexShaders", IsNullable = false)]
		[XmlArrayItem("ShaderObject", IsNullable = false)]
		public List<LibraryObjectRef> VertexShaders = new List<LibraryObjectRef>();

		/// <summary>
		/// Geometry shader objects composing this shader library program.
		/// </summary>
		[XmlArray("GeometryShaders", IsNullable = false)]
		[XmlArrayItem("ShaderObject", IsNullable = false)]
		public List<LibraryObjectRef> GeometryShaders = new List<LibraryObjectRef>();

		/// <summary>
		/// Tessellation shader objects composing this shader library program.
		/// </summary>
		[XmlArray("TessControlShaders", IsNullable = false)]
		[XmlArrayItem("ShaderObject", IsNullable = false)]
		public List<LibraryObjectRef> TessControlShaders = new List<LibraryObjectRef>();

		/// <summary>
		/// Tessellation shader objects composing this shader library program.
		/// </summary>
		[XmlArray("TessEvalShaders", IsNullable = false)]
		[XmlArrayItem("ShaderObject", IsNullable = false)]
		public List<LibraryObjectRef> TessEvalShaders = new List<LibraryObjectRef>();

		/// <summary>
		/// Fragment shader objects composing this shader library program.
		/// </summary>
		[XmlArray("FragmentShaders", IsNullable = false)]
		[XmlArrayItem("ShaderObject", IsNullable = false)]
		public List<LibraryObjectRef> FragmentShaders = new List<LibraryObjectRef>();

		/// <summary>
		/// The program semantics.
		/// </summary>
		[XmlArray("Semantics", IsNullable = false)]
		[XmlArrayItem("Semantic", IsNullable = false)]
		public List<ShaderAttributeSemantic> Semantics = new List<ShaderAttributeSemantic>();

		/// <summary>
		/// Serialize a class inherited by ShaderProgram representing this shader library object.
		/// </summary>
		/// <param name="sw"></param>
		/// <param name="libraryModule"></param>
		public void Generate(StreamWriter sw, IEnumerable<ShaderLibraryConfiguration> libraries, ShaderLibraryModule libraryModule)
		{
			if (VertexShaders.Count == 0)
				throw new InvalidOperationException("no vertex shader");
			if (FragmentShaders.Count == 0)
				throw new InvalidOperationException("no fragment shader");

			sw.WriteLine("	/// <summary>");
			sw.WriteLine("	/// Predefined shader program.");
			sw.WriteLine("	/// </summary>");
			sw.WriteLine("	public class {0} : ShaderProgram", ClassName);
			sw.WriteLine("	{");
			sw.WriteLine("		#region Constructors");
			sw.WriteLine();
			sw.WriteLine("		/// <summary>");
			sw.WriteLine("		/// Construct a {0}.", ClassName);
			sw.WriteLine("		/// </summary>");
			sw.WriteLine("		public {0}() : base(\"{0}\")", ClassName);
			sw.WriteLine("		{");

			if (VertexShaders.Count > 0) {
				sw.WriteLine("			// Attach all required vertex shaders");
				foreach (LibraryObjectRef sObject in VertexShaders)
					AttachShader(sw, sObject, ShaderLibraryObject.Stage.Vertex, libraries);
			}

			if (GeometryShaders.Count > 0) {
				sw.WriteLine();
				sw.WriteLine("			// Attach all required geometry shaders");
				foreach (LibraryObjectRef sObject in GeometryShaders)
					AttachShader(sw, sObject, ShaderLibraryObject.Stage.Geometry, libraries);
			}

			if (FragmentShaders.Count > 0) {
				sw.WriteLine();
				sw.WriteLine("			// Attach all required fragment shaders");
				foreach (LibraryObjectRef sObject in FragmentShaders)
					AttachShader(sw, sObject, ShaderLibraryObject.Stage.Fragment, libraries);
			}

			if (Semantics.Count > 0) {
				sw.WriteLine();
				sw.WriteLine("			// This program defines its own attribute semantics");
				foreach (ShaderAttributeSemantic attributeSemantic in Semantics) {
					sw.WriteLine("			SetAttributeSemantic(\"{0}\", \"{1}\");", attributeSemantic.AttributeName, attributeSemantic.AttributeSemantic);
				}
			}

			sw.WriteLine("		}");
			sw.WriteLine();
			sw.WriteLine("		/// <summary>");
			sw.WriteLine("		/// A string that identifies this ShaderProgram implementation in ShaderLibraryConfiguration.");
			sw.WriteLine("		/// </summary>");
			sw.WriteLine("		public static readonly string LibraryId = \"{0}\";", ClassName);
			sw.WriteLine();
			sw.WriteLine("		#endregion");

			bool hasVertexInterfaces = VertexShaders.Exists(delegate(LibraryObjectRef shaderObject) { return (String.IsNullOrEmpty(shaderObject.Interface) == false); });
			bool hasGeometryxInterfaces = GeometryShaders.Exists(delegate(LibraryObjectRef shaderObject) { return (String.IsNullOrEmpty(shaderObject.Interface) == false); });
			bool hasFragmentInterfaces = FragmentShaders.Exists(delegate(LibraryObjectRef shaderObject) { return (String.IsNullOrEmpty(shaderObject.Interface) == false); });

			if (hasVertexInterfaces || hasGeometryxInterfaces || hasFragmentInterfaces) {

				sw.WriteLine();
				sw.WriteLine("		#region ShaderProgram Overrides");
				sw.WriteLine();
				sw.WriteLine("		/// <summary>");
				sw.WriteLine("		/// Construct a {0}.", ClassName);
				sw.WriteLine("		/// </summary>");
				sw.WriteLine("		protected override void LinkInterfaces()");
				sw.WriteLine("		{");

				if (hasVertexInterfaces) {
					sw.WriteLine("			// Implements all required vertex shaders interfaces");
					foreach (LibraryObjectRef sObject in VertexShaders)
						LazyAttachShader(sw, sObject, ShaderLibraryObject.Stage.Vertex);
				}
				if (hasGeometryxInterfaces) {
					sw.WriteLine("			// Implements all required geometry shaders interfaces");
					foreach (LibraryObjectRef sObject in GeometryShaders)
						LazyAttachShader(sw, sObject, ShaderLibraryObject.Stage.Geometry);
				}
				if (hasFragmentInterfaces) {
					sw.WriteLine("			// Implements all required fragment shaders interfaces");
					foreach (LibraryObjectRef sObject in FragmentShaders)
						LazyAttachShader(sw, sObject, ShaderLibraryObject.Stage.Fragment);
				}

				sw.WriteLine("		}");
				sw.WriteLine();
				sw.WriteLine("		#endregion");
			}
			sw.WriteLine();
			sw.WriteLine("	}");
			sw.WriteLine();
		}

		private void AttachShader(StreamWriter sw, LibraryObjectRef sObject, ShaderLibraryObject.Stage type, IEnumerable<ShaderLibraryConfiguration> libraries)
		{
			if (String.IsNullOrEmpty(sObject.Interface)) {
				ShaderLibraryObject shaderObjectClass = null;

				foreach (ShaderLibraryConfiguration libraryConfiguration in libraries) {
					if ((shaderObjectClass = libraryConfiguration.GetObject(sObject.ObjectId)) != null)
						break;
				}
				
				if (shaderObjectClass == null)
					throw new ArgumentException(type.ToString().ToLower()+" shader object "+sObject.ObjectId+" is not defined");
				if ((shaderObjectClass.ObjectStage != type) && (shaderObjectClass.ObjectStage != ShaderLibraryObject.Stage.Any))
					throw new ArgumentException(String.Format("shader object {0} can't be a {1} shader", sObject.ObjectId, type.ToString().ToLower()));

				sw.WriteLine("			AttachShader({0}.LibraryId, ShaderObject.Stage.{1});", sObject.ObjectId, type.ToString());
			} else {
				sw.WriteLine("			// Lazy attach for shader {0} ({1}): implements {2} interface", sObject.ObjectId, type.ToString(), sObject.Interface);
			}
		}

		private void LazyAttachShader(StreamWriter sw, LibraryObjectRef sObject, ShaderLibraryObject.Stage type)
		{
			if (String.IsNullOrEmpty(sObject.Interface) == false) {
				sw.WriteLine("			if (HasInterface(\"{0}\") == false)", sObject.Interface);
				sw.WriteLine("				AttachShader({0}.LibraryId, ShaderObject.Stage.{1});", sObject.ObjectId, type.ToString());
			}
		}
	}
}
